#pragma once

#include "../interface/Address.hpp"
#include "../interface/Socket.hpp"
#include "../reactor/Reactor.hpp"
#include "../../utility/singleton/Singleton.hpp"

namespace network {

    class TcpServer;

    class ClientConnection : public Socket {

    public:

        using recvCallBack = std::function<void(ClientConnection *)>;

        using closeCallBack = std::function<void(ClientConnection *)>;

        friend class TcpServer;

        /**
          * @brief  创建一个客户端连接
          * @note   默认创建socket、连接状态为断开、reactor为默认单例
          */
        ClientConnection() : Socket(Socket::createSocket()), connected_(false),
                             reactor_(singleton::Singleton<Reactor>::getInstance()) {}

        explicit ClientConnection(Reactor *reactor) : ClientConnection(Socket::createSocket(), false, reactor) {}

        explicit ClientConnection(const Socket &socket, bool connected = false,
                                  Reactor *reactor = singleton::Singleton<Reactor>::getInstance()) : ClientConnection(
            socket.getFd(), Address(socket.getIp(), socket.getPort()), connected, reactor) {}

        explicit ClientConnection(int fd, Address address = Address("127.0.0.1", 8080), bool connected = false,
                                  Reactor *reactor = singleton::Singleton<
                                      Reactor>::getInstance()) : Socket(fd, address), connected_(connected),
                                                                 reactor_(reactor) {}

        /**
        * @brief  连接服务端
        * @param  address 服务端地址
        * @retval 是否连接成功
        */
        bool connect(Address address);

        bool connect(const char *ip, unsigned short port) { return connect(Address(ip, port)); }

        bool connect(const std::string &ip, unsigned short port) { return connect(Address(ip, port)); }

        /**
          * @brief  检测连接状态
          * @retval 连接状态
          */
        bool isConnected() const { return connected_; }

        /**
          * @brief  从读缓冲区读取数据
          * @note   将清空读缓冲区
          * @retval 读缓冲区数据
          */
        std::string read();

        /**
          * @brief  向写缓冲区写入数据并异步发送
          * @note   如果当前未连接则抛出std::runtime_error异常
          * @param  data 要发送的数据
          */
        void send(const std::string &data);

        /**
          * @brief  与服务端断开连接
          */
        void disconnect() const;

        /**
          * @brief  设置客户端断开连接时的回调函数
          * @param  cb 回调函数
          */
        void setRecvCallBack(recvCallBack cb) { recv_cb_ = std::move(cb); }

        /**
          * @brief  设置客户端断开连接时的回调函数
          * @param  cb 回调函数
          */
        void setCloseCallBack(closeCallBack cb) { close_cb_ = std::move(cb); }

    private:

        using Socket::createSocket;

        // 是否连接成功
        bool connected_;

        // 读写缓冲区
        std::string read_buffer_, write_buffer_;

        // 客户端收数据回调函数
        recvCallBack recv_cb_;

        // 客户端断开连接回调函数
        closeCallBack close_cb_;

    protected:

        // 客户端读事件处理
        void clientRecv();

        // 客户端关闭连接事件处理
        void clientClose();

        // 客户端写事件处理
        void clientSend();

        // 反应堆指针
        Reactor *reactor_;

    };

    bool ClientConnection::connect(Address address) {
        int ret = ::connect(fd_, address.getSockAddr(), sizeof(*address.getSockAddr()));
        return connected_ = (ret == 0);
    }

    std::string ClientConnection::read() {
        std::string tmp = read_buffer_;
        read_buffer_.clear();
        return tmp;
    }

    void ClientConnection::send(const std::string &data) {
        if (!connected_) throw std::runtime_error("client not connected");
        write_buffer_.append(data);
        reactor_->addEvent(fd_, EventType::Write);
    }

    void ClientConnection::disconnect() const {
        if (connected_) {
            reactor_->addEvent(fd_, EventType::Close);
        }
    }

#if defined(WIN32) || defined(_WIN32)

    void ClientConnection::clientRecv() {
        std::string message;
        char read_buffer[1024] = {0};
        size_t read_buffer_size = 1024, read_len = 0;
        while (true) {
            read_len = ::recv(fd_, read_buffer, read_buffer_size, 0);
            if (read_len == 0) {
                disconnect();
                break;
            } else if (read_len == SOCKET_ERROR) {
                int error = WSAGetLastError();
                if (error == WSAEWOULDBLOCK) {
                    if (message.empty()) {
                        break;
                    }
                    read_buffer_ = message;
                    if (recv_cb_ != nullptr) {
                        recv_cb_(this);
                    }
                    break;
                } else if (error == WSAEINTR) {
                    continue;// 来自系统内部中断，重试recv即可
                } else {
                    message.clear();// 读数据出现异常了，清空读缓冲区并退出即可
                    break;
                }
            } else {
                message.append(read_buffer, read_len);
            }
        }
    }

#else

    void ClientConnection::clientRecv() {
        std::string message;
        char read_buffer[1024] = {0};
        size_t read_buffer_size = 1024, read_len = 0;
        while (true) {
            read_len = ::recv(fd_, read_buffer, read_buffer_size, 0);
            if (read_len == 0) {
                disconnect();
                break;
            }
            else if (read_len == -1) {
                if (errno == EAGAIN || errno == EWOULDBLOCK) {
                    read_buffer_ = message;
                    if (recv_cb_ != nullptr) {
                        recv_cb_(this);
                    }
                    break;
                }
                else if (errno == EINTR) {
                    continue; // 来自系统内部中断，重试recv即可
                }
                else {
                    message.clear(); // 读数据出现异常了，清空读缓冲区并退出即可
                    break;
                }
            }
            else {
                message.append(read_buffer, read_len);
            }
        }
    }

#endif

    void ClientConnection::clientSend() {
        char *send_buffer = &write_buffer_[0];
        size_t total_size = write_buffer_.size();
        ssize_t send_len = 0, ret = 0;
        while (total_size > send_len) {
            ret = ::send(fd_, send_buffer + send_len, total_size, 0);
            if (ret == -1) {
                if (errno == EAGAIN || errno == EWOULDBLOCK) {
                    break;
                }
                disconnect();
                return;
            }
            send_len += ret;
        }
        if (total_size > send_len) {
            write_buffer_.erase(0, send_len);
            reactor_->changeEventType(fd_, EventType::Write);
        }
        else {
            write_buffer_.clear();
            reactor_->changeEventType(fd_, EventType::Read);
        }
    }

    void ClientConnection::clientClose() {
        if (close_cb_ != nullptr) {
            close_cb_(this);
        }
        connected_ = false;
        reactor_->removeHandler(fd_);
        closeFd();
    }

}
